package com.huabo.sql.builder;

import com.huabo.sql.record.DomainModelAnalysis;
import com.huabo.sql.record.DomainModelContext;
import com.huabo.sql.record.TableColumn;
import com.huabo.utils.SpringContextHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * select sql语句构建工具类
 */
public class SelectBuilder {

    private List<String> columnList = new ArrayList<String>();
    private List<Join> joinList = new ArrayList<Join>();
    private static final ThreadLocal<List<Column>> extendColumnList = new ThreadLocal<List<Column>>();
    private DomainModelContext commonModelContext = null;
    private Class<?> fromClass;

    private static final String FROM_OPERATOR = " FROM ";
    private static final String LEFT_JOIN_OPERATOR = " LEFT JOIN ";
    private static final String RIGHT_JOIN_OPERATOR = " RIGHT JOIN ";
    private static final String INNER_JOIN_OPERATOR = " INNER JOIN ";

    private String getTableName(Class<?> cls) {
        DomainModelAnalysis domainModelAnysis = getDomainModelAnalysis(cls);
        String tableName = domainModelAnysis.getTableName();
        Assert.notNull(tableName, String.format("[%s] is not injection CommonModelContext.", cls.getName()));
        return tableName;
    }

    private DomainModelAnalysis getDomainModelAnalysis(Class<?> cls) {
        Assert.notNull(cls);
        if (commonModelContext == null) {
            commonModelContext = SpringContextHelper.getBean(DomainModelContext.class);
        }
        DomainModelAnalysis domainModelAnysis = commonModelContext.getDomainModelAnalysis(cls);
        Assert.notNull(domainModelAnysis, String.format("[%s] is not injection CommonModelContext.", cls.getName()));
        return domainModelAnysis;
    }

    public SelectBuilder column(String column) {
        columnList.add(column);
        return this;
    }

    /**
     * 自定义映射规则
     *
     * @param column    数据库列名
     * @param mappingTo java属性名，如：user.name, role.id
     * @return
     */
    public SelectBuilder column(String column, String mappingTo) {
        columnList.add(column);
        if (!mappingTo.contains(".")) {
            return this;
        }

        String[] array = mappingTo.split("\\.");
        Field manyToOneField = FieldUtils.getDeclaredField(fromClass, array[0]);
        Assert.notNull(manyToOneField, String.format("[%s] : file name [%s] is not exists.", fromClass.getName(), array[0]));

        Class<?> manyToOneFieldClass = manyToOneField.getType();

        DomainModelAnalysis manyToOneDomainModelAnysis = getDomainModelAnalysis(manyToOneFieldClass);
        TableColumn tableColumn = manyToOneDomainModelAnysis.getTableColumn2(array[1]);
        if (tableColumn == null) {
            tableColumn = manyToOneDomainModelAnysis.getTableColumn(array[1]);
        }
        Assert.notNull(tableColumn, String.format("[%s] : file name [%s] is not exists.", manyToOneFieldClass.getName(), manyToOneField.getName()));

        Column column1 = new Column();
        column1.setDbName(column);
        column1.setFieldName(mappingTo);
        column1.setManyToOneClass(manyToOneFieldClass);
        column1.setManyToOneTableColumn(tableColumn);

        String setMethodName = "set" + StringUtils.capitalize(manyToOneField.getName());
        Method setMethod = ReflectionUtils.findMethod(fromClass, setMethodName, manyToOneFieldClass);
        Assert.notNull(setMethod, String.format("[%s] : file name [%s] does not has set method. Expect method : %s", fromClass.getName(), manyToOneField.getName(), setMethodName));
        column1.setSetModelMethod(setMethod);

        String getMethodName = "get" + StringUtils.capitalize(manyToOneField.getName());
        Method getMethod = ReflectionUtils.findMethod(manyToOneFieldClass, getMethodName);
        Assert.notNull(getMethod, String.format("[%s] : file name [%s] does not has get method. Expect method : %s", fromClass.getName(), manyToOneField.getName(), getMethodName));
        column1.setGetModelMethod(getMethod);

        List<Column> list = extendColumnList.get();
        if (list == null) {
            list = new ArrayList<Column>();
            extendColumnList.set(list);
        }
        list.add(column1);
        return this;
    }

    /**
     * 获取扩展的映射列信息
     *
     * @param columnName 数据库列名
     * @return
     */
    public static Column getExtendColumn(String columnName) {
        List<Column> list = extendColumnList.get();
        if (list == null) {
            return null;
        }
        for (Column column : list) {
            if (column.getDbName().equals(columnName)) {
                return column;
            }
        }
        return null;
    }

    /**
     * 移除扩展的列信息
     */
    public static void removeExtendColumn() {
        extendColumnList.remove();
    }

    public Join from(String tableName) {
        Join join = new Join(tableName, FROM_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join leftJoin(String tableName) {
        Join join = new Join(tableName, LEFT_JOIN_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join rightJoin(String tableName) {
        Join join = new Join(tableName, RIGHT_JOIN_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join innerJoin(String tableName) {
        Join join = new Join(tableName, INNER_JOIN_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join join(String tableName) {
        return innerJoin(tableName);
    }

    public Join from(Class<?> cls) {
        Join join = new Join(getTableName(cls), FROM_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join leftJoin(Class<?> cls) {
        Join join = new Join(getTableName(cls), LEFT_JOIN_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join rightJoin(Class<?> cls) {
        Join join = new Join(getTableName(cls), RIGHT_JOIN_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join innerJoin(Class<?> cls) {
        Join join = new Join(getTableName(cls), INNER_JOIN_OPERATOR);
        joinList.add(join);
        return join;
    }

    public Join join(Class<?> cls) {
        return innerJoin(cls);
    }

    public String getSql() {
        StringBuilder sql = new StringBuilder("SELECT");
        for (int i = 0, size = columnList.size(); i < size; i++) {
            sql.append(" ").append(columnList.get(i));
            if (i != size - 1) {
                sql.append(",");
            }
        }
        for (int i = 0, size = joinList.size(); i < size; i++) {
            Join join = joinList.get(i);
            sql.append(join.getOperator());
            sql.append(join.getTableName());
            if (StringUtils.isNotBlank(join.getAlias())) {
                sql.append(" ").append(join.getAlias());
            }
            if (StringUtils.isNotBlank(join.getOn())) {
                sql.append(" ON ").append(join.getOn());
            }
        }
        return sql.toString().trim().toUpperCase();
    }

    @Override
    public String toString() {
        return getSql();
    }

    public Class<?> getFromClass() {
        return fromClass;
    }

    public void setFromClass(Class<?> fromClass) {
        this.fromClass = fromClass;
    }

    public class Column {

        private String dbName;//数据库字段名称，如_name, ta._name
        private String fieldName;//java字段名称，如：role.name, user.id
        private Class<?> manyToOneClass;//领域模型内引用的其他模型的class
        private Method setModelMethod;//领域模型内引用的其他模型的set method, rowMapper调用
        private Method getModelMethod;//领域模型内引用的其他模型的get method, rowMapper调用
        private TableColumn manyToOneTableColumn;//领域模型内引用的其他模型的信息

        public String getDbName() {
            return dbName;
        }

        public void setDbName(String dbName) {
            this.dbName = dbName;
        }

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public TableColumn getManyToOneTableColumn() {
            return manyToOneTableColumn;
        }

        public void setManyToOneTableColumn(TableColumn manyToOneTableColumn) {
            this.manyToOneTableColumn = manyToOneTableColumn;
        }

        public Method getSetModelMethod() {
            return setModelMethod;
        }

        public void setSetModelMethod(Method setModelMethod) {
            this.setModelMethod = setModelMethod;
        }

        public Method getGetModelMethod() {
            return getModelMethod;
        }

        public void setGetModelMethod(Method getModelMethod) {
            this.getModelMethod = getModelMethod;
        }

        public Class<?> getManyToOneClass() {
            return manyToOneClass;
        }

        public void setManyToOneClass(Class<?> manyToOneClass) {
            this.manyToOneClass = manyToOneClass;
        }
    }

    public class Join {
        private String tableName;
        private String alias = "";
        private String on = "";
        private String operator = "";//操作符，from, inner join, left join, right join

        public Join() {

        }

        public Join(String tableName) {
            this.tableName = tableName;
        }

        public Join(String tableName, String operator) {
            this(tableName);
            this.operator = operator;
        }

        public Join alias(String alias) {
            this.alias = alias;
            return this;
        }

        public Join on(String on) {
            this.on = on;
            return this;
        }

        public SelectBuilder build() {
            return SelectBuilder.this;
        }

        public String getTableName() {
            return tableName;
        }

        public void setTableName(String tableName) {
            this.tableName = tableName;
        }

        public String getAlias() {
            return alias;
        }

        public void setAlias(String alias) {
            this.alias = alias;
        }

        public String getOn() {
            return on;
        }

        public void setOn(String on) {
            this.on = on;
        }

        public String getOperator() {
            return operator;
        }

        public void setOperator(String operator) {
            this.operator = operator;
        }
    }

    /*public static void main(String[] args) {
        SelectBuilder selectBuilder = new SelectBuilder();
        selectBuilder.column("A.*")
                .column("b._NAME TEMPLATE_NAME")
                .column("C._NAME CREATE_NAME")
                .column("d._NAME ORG_NAME")
                .from("TT_TASK_CYCLE").alias("A").build()
                .leftJoin("TT_REPORT_TEMPLATE").alias("B").on("A._REF_ID = b._ID").build()
                .leftJoin("TS_USER").alias("c").on("A ._CREATE_ID = C._ID").build()
                .leftJoin("TS_ORGANIZATION").alias("d").on("C._ORG_ID = d._ID").build();
        System.out.println(selectBuilder);
    }*/
}
